﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using B303Langf.Parser.AST;
using B303Langf.Parser.ContextualAnalysis;

namespace B303Langf.Parser.CodeGenerator
{
   public class Encoder : IVisitor
   {
      private Instruction[] m_Code = new Instruction[ 1024 ];
      private EntityAddress m_CurrentAddress;
      private int m_NextCodeAddress = Machine.CB;
      private int m_JumpAddressForReturn = 0;

      // OPCODE(LENGTH) DISPLACEMENT[REGISTER]

      private void Emit( int opCode, int length, int register, int displacement )
      {
         if( length > 255 )
         {
            throw new CodeGenerationException( "Operand too long" );
         }

         Instruction instr = new Instruction
         {
            OpCode = opCode,
            Length = length,
            Register = register,
            Displacement = displacement
         };

         if( m_NextCodeAddress >= Machine.PB )
         {
            throw new CodeGenerationException( "Program too long" );
         }
         else
         {
            m_Code[ m_NextCodeAddress++ ] = instr;
         }
      }

      private void Patch( int adr, int d )
      {
         m_Code[ adr ].Displacement = d;
      }

      private int DisplayRegister( EntityAddress currentAddress, EntityAddress entityAddress )
      {
         if( entityAddress.Level == 0 )
         {
            return Machine.SBr;
         }
         else if( currentAddress.Level - entityAddress.Level <= 6 )
         {
            return Machine.LBr + currentAddress.Level - entityAddress.Level;
         }
         else
         {
            throw new CodeGenerationException( "Accessing across too many levels" );
         }
      }

      public void Save( Stream stream )
      {
         var writer = new BinaryWriter( stream );
         foreach( var instruction in m_Code )
         {
            if( instruction != null )
            {
               instruction.Write( writer );
            }
            else
            {
               break;
            }
         }
         writer.Flush();
         stream.Close();
         stream.Dispose();
      }

      public void Encode( Program p )
      {
         p.Visit( this, null );
      }

      #region IVisitor Members

      public object VisitProgram( Program program, object args )
      {
         // start address. This will not be passed as argument, but instead be kept track of, as a field
         m_CurrentAddress = new EntityAddress( 0, 0 );

         // visit all variable declarations. We visit these first, because they must be declared before the initial call
         foreach( var decl in program.Declarations.Where( x => !( x is FunctionDeclaration ) ) )
         {
            decl.Visit( this, null );
         }

         // emit call to start function (enter) (must be back-patched)
         int before = m_NextCodeAddress;
         Emit( Machine.CALLop, Machine.SBr, Machine.CBr, 0 );

         // emit halt
         Emit( Machine.HALTop, 0, 0, 0 );

         // emit all function declarations and find the start function
         FunctionDeclaration enterFunc = null;
         foreach( var decl in program.Declarations.Where( x => x is FunctionDeclaration ) )
         {
            decl.Visit( this, null );
            if( decl.Identifier.Spelling == "enter" )
            {
               enterFunc = (FunctionDeclaration) decl;
            }
         }

         // backpatch call to enter function
         Patch( before, enterFunc.Description.Address.Displacement );

         return null;
      }

      //
      // THE FOLLOWING METHODS ACT AS "EXPRESSION VISITS"
      //
      // Expression Visits takes:
      //   ExpressionResult enum. This is a representation of what should be pushed onto the stack
      //   once the expression has been evaluated. This can be: None, Value, Address
      //
      // Expression Visits returns:
      //   Unbalanced offset. This is an int, which represents if an array (char array) was pushed
      //   onto the stack. This is needed because pointers are used to point to these, but there is
      //   currently no other way to know if a char array was defined than with this offset.
      //

      public object VisitArrayIndexer( ArrayIndexer arrayIndexer, object args )
      {
         // simply visit the expression, pass in args specifying if result is stored
         return arrayIndexer.IndexExpression.Visit( this, args );
      }

      public object VisitExpressions( Expressions expressions, object args )
      {
         int unbalancedDisplacement = 0;
         foreach( var expr in expressions.List )
         {
            unbalancedDisplacement += (int) expr.Visit( this, ExpressionResult.Value );
         }
         return unbalancedDisplacement;
      }

      public object VisitTypeLiteralPrimaryExpression( TypeLiteralPrimaryExpression typeLiteralPrimaryExpression, object args )
      {
         return typeLiteralPrimaryExpression.TypeLiteral.Visit( this, args );
      }

      public object VisitOperatorModifiedExpressionPrimaryExpression( OperatorModifiedExpressionPrimaryExpression operatorModifiedExpressionPrimaryExpression, object args )
      {
         ExpressionResult resultNeeded = (ExpressionResult) args;

         var op = operatorModifiedExpressionPrimaryExpression.Operator;
         if( op is MinusOperator )
         {
            operatorModifiedExpressionPrimaryExpression.Expression.Visit( this, resultNeeded );
            if( resultNeeded == ExpressionResult.Value )
            {
               Emit( Machine.CALLop, 0, Machine.PBr, Machine.negDisplacement );
            }
         }
         else if( op is PlusOperator )
         {
            operatorModifiedExpressionPrimaryExpression.Expression.Visit( this, resultNeeded );
         }
         else
         {
            throw new CodeGenerationException( "Can only modify quantities with plus or minus" );
         }

         return 0;
      }

      public object VisitParanthesisPrimaryExpression( ParanthesisPrimaryExpression paranthesisPrimaryExpression, object args )
      {
         return paranthesisPrimaryExpression.Expression.Visit( this, args );
      }

      public object VisitFunctionCallPrimaryExpression( FunctionCallPrimaryExpression functionCallPrimaryExpression, object args )
      {
         ExpressionResult resultNeeded = (ExpressionResult) args;

         // evaluate arguments. We do not need to retrieve the unbalanced offset
         // for this expression because we disallow giving "direct" char array arguments
         // to a function:
         // 
         // This is disallowed:
         // myFunction( "LOL" )?
         //
         // While this is allowed:
         // symbol<> l = "LOL"?
         // myFunction( l )?
         functionCallPrimaryExpression.Expressions.Visit( this, null );

         var info = functionCallPrimaryExpression.Declaration.Description;
         int register = DisplayRegister( m_CurrentAddress, info.Address );

         // Make the function call
         Emit( Machine.CALLop, register, Machine.CB, info.Address.Displacement );

         var returnType = functionCallPrimaryExpression.Declaration.Type;
         if( !( returnType is VoidTypeDenoter ) && resultNeeded != ExpressionResult.Value )
         {
            // pop result if we dont want to use it
            Emit( Machine.POPop, 0, 0, 1 );
         }

         return 0;
      }

      public object VisitArrayVariablePrimaryExpression( ArrayVariablePrimaryExpression arrayVariablePrimaryExpression, object args )
      {
         ExpressionResult resultNeeded = (ExpressionResult) args;

         // get address of variable, so we can load the value of it
         var info = arrayVariablePrimaryExpression.Declaration.Description;
         int register = DisplayRegister( m_CurrentAddress, info.Address );

         // when we visit a array variable primary expresssion, we may EITHER be interested in an address OR a value
         // that is: We may want to know the value of arr<5>, so it can be used further in an expression
         // or the address of it, so we can load a value into it
         if( resultNeeded == ExpressionResult.Value || resultNeeded == ExpressionResult.Address )
         {
            // Load the address of the array (arr[0]), which is the value of the variable
            Emit( Machine.LOADop, 1, register, info.Address.Displacement );

            // visit the indexing expression to load the index
            arrayVariablePrimaryExpression.ArrayIndexer.Visit( this, ExpressionResult.Value );

            // add the address of arr[0] with the array indexer, to get arr[i]
            Emit( Machine.CALLop, 0, Machine.PBr, Machine.addDisplacement );

            // If we are interested in the value, load the value from the address, otherwise, just leave the address
            // on the stack
            if( resultNeeded == ExpressionResult.Value )
            {
               Emit( Machine.LOADIop, arrayVariablePrimaryExpression.Declaration.Type.Size, 0, 0 );
            }
         }
         else // if result is not needed
         {
            // we just want to visit the indexer expression and not do anything else
            arrayVariablePrimaryExpression.ArrayIndexer.Visit( this, ExpressionResult.None );
         }

         return 0;
      }

      public object VisitVariablePrimaryExpression( VariablePrimaryExpression variablePrimaryExpression, object args )
      {
         ExpressionResult resultNeeded = (ExpressionResult) args;

         // get address of variable, so we can load the value of it
         var info = variablePrimaryExpression.Declaration.Description;
         int register = DisplayRegister( m_CurrentAddress, info.Address );

         // Load the value of the expression onto the stack, if the value is wanted
         if( resultNeeded == ExpressionResult.Value )
         {
            Emit( Machine.LOADop, 1, register, info.Address.Displacement );
         }

         return 0;
      }

      public object VisitBinaryExpression( BinaryExpression binaryExpression, object args )
      {
         ExpressionResult resultNeeded = (ExpressionResult) args;

         // get the operator to determine what to do next
         var op = binaryExpression.Operator;

         if( op is AssignOperator )
         {
            // we know that the left operand is an operand that supports "being a variable declaration", if it is left-side
            var idExpression = binaryExpression.LeftPrimary as ISupportsVariableDeclaration;

            // we use this idExpression to find the address, of the left-side
            var leftInfo = idExpression.Declaration.Description;
            int leftRegister = DisplayRegister( m_CurrentAddress, leftInfo.Address );
            
            var rightExpressionInfo = binaryExpression.RightPrimary.Info;

            // visit the left-side specifying we do not want anything on the stack
            binaryExpression.LeftPrimary.Visit( this, ExpressionResult.None );
            
            // direct type. 
            if( rightExpressionInfo.ExpressionType == ExpressionType.Variable || !rightExpressionInfo.TypeDenoter.IsArray )
            {
               // This handles the following case:
               // id1 is id2?
               // id1 is VALUE? (VALUE cannot be an char array)
               //

               // evaluate right-side of expression
               binaryExpression.RightPrimary.Visit( this, ExpressionResult.Value );

               // special handling is required if right-side is a array variable
               // because we need to load the variable into a special address
               if( binaryExpression.LeftPrimary is ArrayVariablePrimaryExpression )
               {
                  // evaluate the right-side of expression AGAIN
                  binaryExpression.RightPrimary.Visit( this, ExpressionResult.Value );

                  // this will be the address
                  binaryExpression.LeftPrimary.Visit( this, ExpressionResult.Address );

                  // store the value of the left-side into the address
                  Emit( Machine.STOREIop, 1, 0, 0 );

                  // if result is not wanted, pop the value that was previously pushed onto the stack (as a duplicate)
                  if( resultNeeded != ExpressionResult.Value )
                  {
                     Emit( Machine.POPop, 0, 0, 1 );
                  }
               }
               else
               {
                  // otherwise, we just need to store the value of the right side in the correct register
                  Emit( Machine.STOREop, 1, leftRegister, leftInfo.Address.Displacement );

                  if( resultNeeded == ExpressionResult.Value )
                  {
                     Emit( Machine.LOADop, 1, leftRegister, leftInfo.Address.Displacement );
                  }
               }

               return 0;
            }
            else
            {
               // This handles the following case:
               // c is "Haha"?
               // 
               int unbalance = (int) binaryExpression.RightPrimary.Visit( this, ExpressionResult.Value );

               // load address of arr[0] to stack
               Emit( Machine.LOADAop, 0, Machine.LBr, m_CurrentAddress.Displacement );

               // store the address of arr[0] to declaration
               Emit( Machine.STOREop, leftInfo.Size, leftRegister, leftInfo.Address.Displacement );

               // return next address
               return unbalance;
            }
         }
         else // if it is something different from an assignment operator
         {
            binaryExpression.LeftPrimary.Visit( this, resultNeeded );
            binaryExpression.RightPrimary.Visit( this, resultNeeded );

            if( resultNeeded == ExpressionResult.Value )
            {
               if( op is PlusOperator )
               {
                  Emit( Machine.CALLop, 0, Machine.PBr, Machine.addDisplacement );
               }
               else if( op is MinusOperator )
               {
                  Emit( Machine.CALLop, 0, Machine.PBr, Machine.subDisplacement );
               }
               else if( op is MultiplyOperator )
               {
                  Emit( Machine.CALLop, 0, Machine.PBr, Machine.multDisplacement );
               }
               else if( op is DivisionOperator )
               {
                  Emit( Machine.CALLop, 0, Machine.PBr, Machine.divDisplacement );
               }
               else if( op is EqualsOperator )
               {
                  Emit( Machine.LOADLop, 0, 0, binaryExpression.LeftPrimary.Info.TypeDenoter.Size );
                  Emit( Machine.CALLop, 0, Machine.PBr, Machine.eqDisplacement );
               }
               else if( op is NotEqualsOperator )
               {
                  Emit( Machine.LOADLop, 0, 0, binaryExpression.LeftPrimary.Info.TypeDenoter.Size );
                  Emit( Machine.CALLop, 0, Machine.PBr, Machine.neDisplacement );
               }
               else if( op is LessThanOperator )
               {
                  Emit( Machine.CALLop, 0, Machine.PBr, Machine.ltDisplacement );
               }
               else if( op is LessThanOrEqualsOperator )
               {
                  Emit( Machine.CALLop, 0, Machine.PBr, Machine.leDisplacement );
               }
               else if( op is GreaterThanOperator )
               {
                  Emit( Machine.CALLop, 0, Machine.PBr, Machine.gtDisplacement );
               }
               else if( op is GreaterThanOrEqualsOperator )
               {
                  Emit( Machine.CALLop, 0, Machine.PBr, Machine.geDisplacement );
               }
               else if( op is ConditionalAndOperator )
               {
                  Emit( Machine.CALLop, 0, Machine.PBr, Machine.andDisplacement );
               }
               else if( op is ConditionalOrOperator )
               {
                  Emit( Machine.CALLop, 0, Machine.PBr, Machine.orDisplacement );
               }
               else
               {
                  throw new CodeGenerationException( "Could not find correct operator" );
               }
            }
         }
         return 0;
      }

      public object VisitBoolTypeLiteral( BoolTypeLiteral boolTypeLiteral, object args )
      {
         ExpressionResult resultNeeded = (ExpressionResult) args;

         if( resultNeeded == ExpressionResult.Value )
         {
            Emit( Machine.LOADLop, 0, 0, boolTypeLiteral.TamValue );
         }
         return 0;
      }

      public object VisitIntegerTypeLiteral( IntegerTypeLiteral integerTypeLiteral, object args )
      {
         ExpressionResult resultNeeded = (ExpressionResult) args;

         if( resultNeeded == ExpressionResult.Value )
         {
            Emit( Machine.LOADLop, 0, 0, integerTypeLiteral.TamValue );
         }
         return 0;
      }

      public object VisitCharTypeLiteral( CharTypeLiteral charTypeLiteral, object args )
      {
         ExpressionResult resultNeeded = (ExpressionResult) args;

         if( resultNeeded == ExpressionResult.Value )
         {
            Emit( Machine.LOADLop, 0, 0, charTypeLiteral.TamValue );
         }
         return 0;
      }

      public object VisitCharArrayTypeLiteral( CharArrayTypeLiteral charArrayTypeLiteral, object args )
      {
         ExpressionResult resultNeeded = (ExpressionResult) args;

         byte[] tamValue = charArrayTypeLiteral.TamValue;
         if( resultNeeded == ExpressionResult.Value )
         {
            // this will cause an unbalance
            foreach( var c in tamValue )
            {
               Emit( Machine.LOADLop, 0, 0, (int) c );
            }
            return tamValue.Length;
         }
         return 0;
      }

      public object VisitBlock( Block block, object args )
      {
         EntityAddress beforeBlock = m_CurrentAddress;

         foreach( var statement in block.Statements )
         {
            statement.Visit( this, null );
         }

         // merry poppins comes cleaning
         // This cleaning is not really required, but comes in really handy
         // if you for example use a loop, such that the values declared
         // in the loop are popped again when leaving an iteration
         int poppins = 0;
         foreach( var declStatement in block.DeclarationStatements )
         {
            poppins += declStatement.Declaration.Description.Size;
         }
         if( poppins > 0 )
         {
            Emit( Machine.POPop, 0, 0, poppins );
         }

         m_CurrentAddress = beforeBlock;
         return null;
      }

      //
      // THE FOLLOWING METHODS ACT AS "STATEMENT VISITS"
      //
      // They expect no argument and return no argument
      //

      public object VisitDeclarationStatement( DeclarationStatement declarationStatement, object args )
      {
         declarationStatement.Declaration.Visit( this, null );
         return null;
      }

      public object VisitExpressionStatement( ExpressionStatement expressionStatement, object args )
      {
         int unbalance = (int) expressionStatement.Expression.Visit( this, ExpressionResult.None );
         m_CurrentAddress = new EntityAddress( m_CurrentAddress, unbalance );
         return null;
      }

      public object VisitForStatement( ForStatement forStatement, object args )
      {
         forStatement.InitializationExpression.Visit( this, ExpressionResult.None );

         int jumpBackToBeginning = m_NextCodeAddress;
         forStatement.ConditionExpression.Visit( this, ExpressionResult.Value );

         int before = m_NextCodeAddress;
         Emit( Machine.JUMPIFop, 0, Machine.CB, 0 );

         forStatement.Block.Visit( this, null );

         forStatement.IncrementationExpression.Visit( this, ExpressionResult.None );

         Emit( Machine.JUMPop, 0, Machine.CB, jumpBackToBeginning );

         Patch( before, m_NextCodeAddress );

         return null;
      }

      public object VisitIfStatement( IfStatement ifStatement, object args )
      {
         ifStatement.ConditionExpression.Visit( this, ExpressionResult.Value );

         int e = m_NextCodeAddress;
         Emit( Machine.JUMPIFop, 0, Machine.CB, 0 );

         ifStatement.IfBlock.Visit( this, null );

         int d = m_NextCodeAddress;
         Emit( Machine.JUMPop, 0, Machine.CB, 0 );

         Patch( e, m_NextCodeAddress );

          Block elseBlock = ifStatement.ElseBlock;
          if(elseBlock != null)
          {
              elseBlock.Visit( this, null );
          }

          Patch( d, m_NextCodeAddress );

         return null;
      }

      public object VisitReturnStatement( ReturnStatement returnStatement, object args )
      {
         // evaluate expression to get return value
         returnStatement.Expression.Visit( this, ExpressionResult.Value );

         // JUMP for return!
         Emit( Machine.JUMPop, 0, Machine.CBr, m_JumpAddressForReturn );

         return null;
      }

      public object VisitWhileStatement( WhileStatement whileStatement, object args )
      {
         int jumpBackToBeginning = m_NextCodeAddress;

         whileStatement.ConditionExpression.Visit( this, ExpressionResult.Value );

         int before = m_NextCodeAddress;
         Emit( Machine.JUMPIFop, 0, Machine.CB, 0 );

         whileStatement.Block.Visit( this, null );

         Emit( Machine.JUMPop, 0, Machine.CB, jumpBackToBeginning );

         Patch( before, m_NextCodeAddress );

         return null;
      }

      public object VisitOutputStatement( OutputStatement outputStatement, object args )
      {
         // load the expression onto the stack. This may very well cause an unbalance because
         // output "Hello"?
         // 
         // is a very likely statement
         var unbalance = outputStatement.Expression.Visit( this, ExpressionResult.Value );

         // we will adjust for this unbalance before returning, as this is a self-contained statement
         
         // get info about expression
         var info = outputStatement.Expression.Info;


         if( info.TypeDenoter is IntegerTypeDenoter && !info.TypeDenoter.IsArray )
         {
            Emit( Machine.CALLop, 0, Machine.PBr, Machine.putintDisplacement );
            Emit( Machine.CALLop, 0, Machine.PBr, Machine.puteolDisplacement );
         }
         else if( info.TypeDenoter is CharTypeDenoter )
         {
            if( info.TypeDenoter.IsArray )
            {
               // if the expression type is a value, no pointer has been pushed onto the stack, which we need.
               if( info.ExpressionType == ExpressionType.Value )
               {
                  int unbalancedOffset = (int) unbalance;

                  Emit( Machine.LOADAop, 0, Machine.LBr, m_CurrentAddress.Displacement );

                  // address of arr[0] is on stack, copy it, pushing it onto the stack
                  int startAddress = m_NextCodeAddress;
                  Emit( Machine.LOADop, 1, Machine.LBr, m_CurrentAddress.Displacement + unbalancedOffset );

                  // load value of the address that was just pushed onto the stack
                  Emit( Machine.LOADIop, 1, 0, 0 );

                  int before = m_NextCodeAddress;
                  Emit( Machine.JUMPIFop, Encoding.ASCII.GetBytes( "\0" )[ 0 ], Machine.CBr, 0 );

                  // reload the expression, since it was popped due to JUMPIF
                  Emit( Machine.LOADop, 1, Machine.LBr, m_CurrentAddress.Displacement + unbalancedOffset );
                  Emit( Machine.LOADIop, 1, 0, 0 );

                  // write the char, removing it from the stack
                  Emit( Machine.CALLop, 0, Machine.PBr, Machine.putDisplacement );

                  // increase the address by one
                  Emit( Machine.CALLop, 0, Machine.PBr, Machine.succDisplacement );

                  // jump back to start
                  Emit( Machine.JUMPop, 0, Machine.CBr, startAddress );

                  Patch( before, m_NextCodeAddress );
                  Emit( Machine.CALLop, 0, Machine.PBr, Machine.puteolDisplacement );

                  // pop the initial expression
                  Emit( Machine.POPop, 0, 0, 1 + unbalancedOffset );
               }
               else // if( info.ExpressionType == ExpressionType.Variable )
               {
                  // address of arr[0] is on stack, copy it, pushing it onto the stack again
                  int startAddress = m_NextCodeAddress;
                  Emit( Machine.LOADop, 1, Machine.LBr, m_CurrentAddress.Displacement );

                  // load value of the address that was just pushed onto the stack
                  Emit( Machine.LOADIop, 1, 0, 0 );

                  int before = m_NextCodeAddress;
                  Emit( Machine.JUMPIFop, Encoding.ASCII.GetBytes( "\0" )[ 0 ], Machine.CBr, 0 );

                  // reload the expression, since it was popped due to JUMPIF
                  Emit( Machine.LOADop, 1, Machine.LBr, m_CurrentAddress.Displacement );
                  Emit( Machine.LOADIop, 1, 0, 0 );

                  // write the char, removing it from the stack
                  Emit( Machine.CALLop, 0, Machine.PBr, Machine.putDisplacement );

                  // increase the address by one
                  Emit( Machine.CALLop, 0, Machine.PBr, Machine.succDisplacement );

                  // jump back to start
                  Emit( Machine.JUMPop, 0, Machine.CBr, startAddress );

                  Patch( before, m_NextCodeAddress );
                  Emit( Machine.CALLop, 0, Machine.PBr, Machine.puteolDisplacement );

                  // pop the initial expression
                  Emit( Machine.POPop, 0, 0, 1 );
               }
            }
            else
            {
               // value of char is on stack, put it!
               Emit( Machine.putDisplacement, 0, 0, 1 );
               Emit( Machine.CALLop, 0, Machine.PBr, Machine.puteolDisplacement );
            }
         }
         else if( info.TypeDenoter is BoolTypeDenoter && !info.TypeDenoter.IsArray )
         {
            // jump if false (empty), also consumes the stack
            int before = m_NextCodeAddress;
            Emit( Machine.JUMPIFop, 0, Machine.CBr, 0 );

            // true case
            Emit( Machine.LOADLop, 0, 0, Encoding.ASCII.GetBytes( "f" )[ 0 ] );
            Emit( Machine.CALLop, 0, Machine.PBr, Machine.putDisplacement );
            Emit( Machine.LOADLop, 0, 0, Encoding.ASCII.GetBytes( "u" )[ 0 ] );
            Emit( Machine.CALLop, 0, Machine.PBr, Machine.putDisplacement );
            Emit( Machine.LOADLop, 0, 0, Encoding.ASCII.GetBytes( "l" )[ 0 ] );
            Emit( Machine.CALLop, 0, Machine.PBr, Machine.putDisplacement );
            Emit( Machine.LOADLop, 0, 0, Encoding.ASCII.GetBytes( "l" )[ 0 ] );
            Emit( Machine.CALLop, 0, Machine.PBr, Machine.putDisplacement );
            Emit( Machine.CALLop, 0, Machine.PBr, Machine.puteolDisplacement );
            int before2 = m_NextCodeAddress;
            Emit( Machine.JUMPop, 0, Machine.CBr, 0 );

            // false case
            Patch( before, m_NextCodeAddress );
            Emit( Machine.LOADLop, 0, 0, Encoding.ASCII.GetBytes( "e" )[ 0 ] );
            Emit( Machine.CALLop, 0, Machine.PBr, Machine.putDisplacement );
            Emit( Machine.LOADLop, 0, 0, Encoding.ASCII.GetBytes( "m" )[ 0 ] );
            Emit( Machine.CALLop, 0, Machine.PBr, Machine.putDisplacement );
            Emit( Machine.LOADLop, 0, 0, Encoding.ASCII.GetBytes( "p" )[ 0 ] );
            Emit( Machine.CALLop, 0, Machine.PBr, Machine.putDisplacement );
            Emit( Machine.LOADLop, 0, 0, Encoding.ASCII.GetBytes( "t" )[ 0 ] );
            Emit( Machine.CALLop, 0, Machine.PBr, Machine.putDisplacement );
            Emit( Machine.LOADLop, 0, 0, Encoding.ASCII.GetBytes( "y" )[ 0 ] );
            Emit( Machine.CALLop, 0, Machine.PBr, Machine.putDisplacement );
            Emit( Machine.CALLop, 0, Machine.PBr, Machine.puteolDisplacement );

            Patch( before2, m_NextCodeAddress );
         }

         return null;
      }

      public object VisitInputStatement( InputStatement inputStatement, object args )
      {
         // load the initial address
         inputStatement.VariableExpression.Visit( this, ExpressionResult.Value );

         // at this point, the intial address of the array to fill, is on the stack

         // load in, a copy of the top of the stack
         int jumpToHere = m_NextCodeAddress;
         Emit( Machine.LOADop, 1, Machine.LBr, m_CurrentAddress.Displacement );

         // check if the next char, is an eol, if it is, jump to end
         Emit( Machine.CALLop, 0, Machine.PBr, Machine.eolDisplacement );
         int before = m_NextCodeAddress;
         Emit( Machine.JUMPIFop, 1, Machine.CBr, 0 ); // to be backpatched

         // load the value to the array
         Emit( Machine.CALLop, 0, Machine.PBr, Machine.getDisplacement );

         // increment address to next address
         Emit( Machine.CALLop, 0, Machine.PBr, Machine.succDisplacement );

         // jump back to beginning for next char
         Emit( Machine.JUMPop, 0, Machine.CBr, jumpToHere );

         // end
         Patch( before, m_NextCodeAddress ); // backpatck JUMPIF

         // write out "end" character
         Emit( Machine.LOADLop, 0, 0, Encoding.ASCII.GetBytes( "\0" )[ 0 ] );
         Emit( Machine.LOADop, 1, Machine.LBr, m_CurrentAddress.Displacement );
         Emit( Machine.STOREIop, 1, 0, 0 );

         // pop the two addresses that populate the top of the stack
         Emit( Machine.POPop, 0, 0, 2 );

         return null;
      }

      //
      // THE FOLLOWING METHODS ACT AS "DECLARATION VISITS"
      //
      // They expect no argument and return no argument
      //

      public object VisitDeclarations( Declarations declarations, object args )
      {
         foreach( var decl in declarations.List )
         {
            // declarations should not be elaborated, because these are always function parameters
            decl.Visit( this, null );
         }
         return null;
      }

      public object VisitFunctionDeclaration( FunctionDeclaration functionDeclaration, object args )
      {
         var beforeAddress = m_CurrentAddress;

         // we use the general solution to static link, even though we artificially prevent people from having a nested functions
         functionDeclaration.Description = new UnknownValue( 1, m_CurrentAddress.Level, m_NextCodeAddress );

         // assign addresses to arguments
         m_CurrentAddress = m_CurrentAddress.NextLevel();
         int functionParameterSize = functionDeclaration.Declarations.CalculateSize();
         m_CurrentAddress.Displacement = -functionParameterSize;
         functionDeclaration.Declarations.Visit( this, null );

         // emit the jump for returning
         int d = m_NextCodeAddress;
         Emit( Machine.JUMPop, 0, Machine.CB, 0 );

         // m_JumpAddressForReturn we can jump to this address from any statement to return
         // because we have it as a field and because we do not have nested functions
         m_JumpAddressForReturn = m_NextCodeAddress;
         if( functionDeclaration.Type is VoidTypeDenoter )
         {
            Emit( Machine.RETURNop, 0, 0, functionParameterSize );
         }
         else
         {
            Emit( Machine.RETURNop, 1, 0, functionParameterSize );
         }

         // update nested address to take links into account
         m_CurrentAddress.Displacement = Machine.LinkDataSize;

         // patch the jump to block start
         Patch( d, m_NextCodeAddress );
         functionDeclaration.Block.Visit( this, null );

         // return if we have not already returned in the block!
         if( functionDeclaration.Type is VoidTypeDenoter )
         {
            Emit( Machine.RETURNop, 0, 0, functionParameterSize );
         }
         else
         {
            Emit( Machine.RETURNop, 1, 0, functionParameterSize );
         }

         // re-assign the m_CurrentAddress to the one before we went into the function declaration
         m_CurrentAddress = beforeAddress;

         return null;
      }

      public object VisitVariableDeclaration( VariableDeclaration variableDeclaration, object args )
      {
         // assign address
         variableDeclaration.Description = new UnknownValue( variableDeclaration.Type.Size, m_CurrentAddress.Level, m_CurrentAddress.Displacement );

         // push to make space available, if, and only if, this is not a part of a function declaration
         // (displacement of the m_CurrentAddress will be negative if this is a function argument)
         if( m_CurrentAddress.Displacement > 0 )
         {
            Emit( Machine.PUSHop, 0, 0, variableDeclaration.Type.Size );

            // if it is an array variable, we may need to make some space on the stack for its value as well.
            if( variableDeclaration.Type.IsArray && variableDeclaration.Type.ArrayIndexer.HasIndex )
            {
               var info = variableDeclaration.Description;
               int register = DisplayRegister( m_CurrentAddress, info.Address );

               // push the uninitalized array onto the stack.
               // we know the index expression is an integer literal (static array)
               var literalExpr = (TypeLiteralPrimaryExpression) variableDeclaration.Type.ArrayIndexer.IndexExpression;
               var integerLit = (IntegerTypeLiteral) literalExpr.TypeLiteral;
               int arraySize = integerLit.TamValue;

               // push the constant value onto the stack
               Emit( Machine.PUSHop, 0, 0, arraySize );

               // load address of arr[0] to stack
               Emit( Machine.LOADAop, 0, register, info.Address.Displacement + 1 );

               // store the address of arr[0] to declaration
               Emit( Machine.STOREop, variableDeclaration.Type.Size, register, info.Address.Displacement );

               // make sure to include the array in the new address returned
               m_CurrentAddress = new EntityAddress( m_CurrentAddress, variableDeclaration.Description.Size + arraySize );
            }
            else
            {
               m_CurrentAddress = new EntityAddress( m_CurrentAddress, variableDeclaration.Description.Size );
            }
         }
         else
         {
            m_CurrentAddress = new EntityAddress( m_CurrentAddress, variableDeclaration.Description.Size );
         }

         return null;
      }

      public object VisitVariableDeclarationWithDefinition( VariableDeclarationWithDefinition variableDeclarationWithDefinition, object args )
      {
         // assign address
         variableDeclarationWithDefinition.Description = new UnknownValue( variableDeclarationWithDefinition.Type.Size, m_CurrentAddress.Level, m_CurrentAddress.Displacement );

         // push to make space available
         Emit( Machine.PUSHop, 0, 0, variableDeclarationWithDefinition.Type.Size );

         // find address of newly pushed variable
         var info = variableDeclarationWithDefinition.Description;
         int register = DisplayRegister( m_CurrentAddress, info.Address );

         if( variableDeclarationWithDefinition.Type.IsArray )
         {
            // load the value to stack (which can ONLY be a char array, causing an imbalance)
            int unbalance = (int) variableDeclarationWithDefinition.DefinitionExpression.Visit( this, ExpressionResult.Value );

            // load address of arr[0] to stack
            Emit( Machine.LOADAop, 0, register, info.Address.Displacement + 1 );

            // store the address of arr[0] to declaration
            Emit( Machine.STOREop, variableDeclarationWithDefinition.Type.Size, register, info.Address.Displacement );

            // return next address
            m_CurrentAddress = new EntityAddress( m_CurrentAddress, variableDeclarationWithDefinition.Description.Size + unbalance );
         }
         else
         {
            // load the value to stack
            variableDeclarationWithDefinition.DefinitionExpression.Visit( this, ExpressionResult.Value );

            // store it in newly made declaration
            Emit( Machine.STOREop, variableDeclarationWithDefinition.Type.Size, register, info.Address.Displacement );

            // return next address
            m_CurrentAddress = new EntityAddress( m_CurrentAddress, variableDeclarationWithDefinition.Description.Size );
         }
         return null;
      }

      // The rest of the methods are unused

      public object VisitIdentifier( Identifier identifier, object args )
      {
         return null;
      }

      public object VisitOperator( Operator _operator, object args )
      {
         return null;
      }

      public object VisitIntegerTypeDenoter( IntegerTypeDenoter integerTypeDenoter, object args )
      {
         return null;
      }

      public object VisitBoolTypeDenoter( BoolTypeDenoter boolTypeDenoter, object args )
      {
         return null;
      }

      public object VisitVoidTypeDenoter( VoidTypeDenoter voidTypeDenoter, object args )
      {
         return null;
      }

      public object VisitCharTypeDenoter( CharTypeDenoter charTypeDenoter, object args )
      {
         return null;
      }

      #endregion
   }
}
